/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.easyseata.tc.coordinator;

import static com.easyseata.core.exception.TransactionExceptionCode.BranchTransactionNotExist;
import static com.easyseata.core.exception.TransactionExceptionCode.FailedToAddBranch;
import static com.easyseata.core.exception.TransactionExceptionCode.FailedToSendBranchCommitRequest;
import static com.easyseata.core.exception.TransactionExceptionCode.FailedToSendBranchRollbackRequest;
import static com.easyseata.core.exception.TransactionExceptionCode.GlobalTransactionNotActive;
import static com.easyseata.core.exception.TransactionExceptionCode.GlobalTransactionStatusInvalid;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import com.easyseata.common.ConfigurationKeys;
import com.easyseata.common.DefaultValues;
import com.easyseata.common.util.StringUtils;
import com.easyseata.config.core.Configuration;
import com.easyseata.config.core.ConfigurationFactory;
import com.easyseata.core.context.RootContext;
import com.easyseata.core.exception.BranchTransactionException;
import com.easyseata.core.exception.GlobalTransactionException;
import com.easyseata.core.exception.TransactionException;
import com.easyseata.core.exception.TransactionExceptionCode;
import com.easyseata.core.lock.LockManager;
import com.easyseata.core.lock.LockerManagerFactory;
import com.easyseata.core.model.BranchStatus;
import com.easyseata.core.model.BranchType;
import com.easyseata.core.model.GlobalStatus;
import com.easyseata.core.protocol.transaction.BranchCommitRequest;
import com.easyseata.core.protocol.transaction.BranchCommitResponse;
import com.easyseata.core.protocol.transaction.BranchRollbackRequest;
import com.easyseata.core.protocol.transaction.BranchRollbackResponse;
import com.easyseata.rm.DefaultResourceManager;
import com.easyseata.tc.session.BranchSession;
import com.easyseata.tc.session.GlobalSession;
import com.easyseata.tc.session.SessionHelper;
import com.easyseata.tc.session.SessionHolder;

/**
 * The type abstract core.
 *
 */
public abstract class AbstractCore implements Core {

	 protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractCore.class);

	    protected LockManager lockManager = LockerManagerFactory.getLockManager();

	    private static final Configuration CONFIG = ConfigurationFactory.getInstance();
	    private int appDataErrSize ;
	    private boolean throwDataSizeExp ;


	    public AbstractCore() {
	        this.appDataErrSize = CONFIG.getInt(ConfigurationKeys.SERVER_APPLICATION_DATA_SIZE_LIMIT,
	            DefaultValues.DEFAULT_APPLICATION_DATA_SIZE_LIMIT);
	        this.throwDataSizeExp = CONFIG.getBoolean(ConfigurationKeys.SERVER_APPLICATION_DATA_SIZE_CHECK, false);

	    }

	    public abstract BranchType getHandleBranchType();

	    @Override
	    public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid,
	                               String applicationData, String lockKeys) throws TransactionException {
	        GlobalSession globalSession = assertGlobalSessionNotNull(xid, false);
	        try {
	            StringUtils.checkDataSize(applicationData, "applicationData", appDataErrSize, throwDataSizeExp);
	        } catch (RuntimeException e) {
	            throw new BranchTransactionException(TransactionExceptionCode.FailedToAddBranch,
	                    String.format("Failed to store branch xid = %s ", globalSession.getXid()), e);
	        }

	        return SessionHolder.lockAndExecute(globalSession, () -> {
	            globalSessionStatusCheck(globalSession);
	            BranchSession branchSession = SessionHelper.newBranchByGlobal(globalSession, branchType, resourceId,
	                    applicationData, lockKeys, clientId);
	            MDC.put(RootContext.MDC_KEY_BRANCH_ID, String.valueOf(branchSession.getBranchId()));
	            branchSessionLock(globalSession, branchSession);
	            try {
	                globalSession.addBranch(branchSession);
	            } catch (RuntimeException ex) {
	                branchSessionUnlock(branchSession);
	                throw new BranchTransactionException(FailedToAddBranch, String
	                        .format("Failed to store branch xid = %s branchId = %s", globalSession.getXid(),
	                                branchSession.getBranchId()), ex);
	            }
	            if (LOGGER.isInfoEnabled()) {
	                LOGGER.info("Register branch successfully, xid = {}, branchId = {}, resourceId = {} ,lockKeys = {}",
	                    globalSession.getXid(), branchSession.getBranchId(), resourceId, lockKeys);
	            }
	            return branchSession.getBranchId();
	        });
	    }

	    protected void globalSessionStatusCheck(GlobalSession globalSession) throws GlobalTransactionException {
	        if (!globalSession.isActive()) {
	            throw new GlobalTransactionException(GlobalTransactionNotActive, String.format(
	                "Could not register branch into global session xid = %s status = %s, cause by globalSession not active",
	                globalSession.getXid(), globalSession.getStatus()));
	        }
	        if (globalSession.getStatus() != GlobalStatus.Begin) {
	            throw new GlobalTransactionException(GlobalTransactionStatusInvalid, String
	                    .format("Could not register branch into global session xid = %s status = %s while expecting %s",
	                            globalSession.getXid(), globalSession.getStatus(), GlobalStatus.Begin));
	        }
	    }

	    protected void branchSessionLock(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {

	    }

	    protected void branchSessionUnlock(BranchSession branchSession) throws TransactionException {

	    }

	    private GlobalSession assertGlobalSessionNotNull(String xid, boolean withBranchSessions)
	            throws TransactionException {
	        GlobalSession globalSession = SessionHolder.findGlobalSession(xid, withBranchSessions);
	        if (globalSession == null) {
	            throw new GlobalTransactionException(TransactionExceptionCode.GlobalTransactionNotExist,
	                    String.format("Could not found global transaction xid = %s, may be has finished.", xid));
	        }
	        return globalSession;
	    }

	    @Override
	    public void branchReport(BranchType branchType, String xid, long branchId, BranchStatus status,
	                             String applicationData) throws TransactionException {
	        GlobalSession globalSession = assertGlobalSessionNotNull(xid, true);
	        BranchSession branchSession = globalSession.getBranch(branchId);
	        if (branchSession == null) {
	            throw new BranchTransactionException(BranchTransactionNotExist,
	                    String.format("Could not found branch session xid = %s branchId = %s", xid, branchId));
	        }
	        branchSession.setApplicationData(applicationData);
	        globalSession.changeBranchStatus(branchSession, status);

	        if (LOGGER.isInfoEnabled()) {
	            LOGGER.info("Report branch status successfully, xid = {}, branchId = {}", globalSession.getXid(),
	                branchSession.getBranchId());
	        }
	    }

	    @Override
	    public boolean lockQuery(BranchType branchType, String resourceId, String xid, String lockKeys)
	            throws TransactionException {
	        return true;
	    }

	    @Override
	    public BranchStatus branchCommit(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
	        try {
	            
	            return DefaultResourceManager.get().branchCommit(branchSession.getBranchType(), branchSession.getXid(),
	            		branchSession.getBranchId(), branchSession.getResourceId(), branchSession.getApplicationData());
	        } catch (Exception e) {
	            throw new BranchTransactionException(FailedToSendBranchCommitRequest,
	                    String.format("Send branch commit failed, xid = %s branchId = %s", branchSession.getXid(),
	                            branchSession.getBranchId()), e);
	        }
	    }


	    @Override
	    public BranchStatus branchRollback(GlobalSession globalSession, BranchSession branchSession) throws TransactionException {
	    	  try {
		            
		            return DefaultResourceManager.get().branchRollback(branchSession.getBranchType(), branchSession.getXid(),
		            		branchSession.getBranchId(), branchSession.getResourceId(), branchSession.getApplicationData());
		        } catch (Exception e) {
		            throw new BranchTransactionException(FailedToSendBranchRollbackRequest,
		                    String.format("Send branch commit failed, xid = %s branchId = %s", branchSession.getXid(),
		                            branchSession.getBranchId()), e);
		        }
	    }


	    @Override
	    public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
	            throws TransactionException {
	        return null;
	    }

	    @Override
	    public GlobalStatus commit(String xid) throws TransactionException {
	        return null;
	    }

	    @Override
	    public boolean doGlobalCommit(GlobalSession globalSession, boolean retrying) throws TransactionException {
	        return true;
	    }
	    
		@Override
		public GlobalStatus rollback(String xid) throws TransactionException {
			return null;
		}

		@Override
		public boolean doGlobalRollback(GlobalSession globalSession, boolean retrying) throws TransactionException {
			return false;
		}


}
